//
//  Shader.m
//  Renderbunny
//
//  Created by Holmes Futrell on 1/22/11.
//  Copyright 2011 Holmes Futrell. All rights reserved.
//

#import "ShaderProgram.h"

@interface ShaderProgram(internalMethods)
-(NSString *)informationLogForShader:(GLuint)shader;
-(BOOL)compileShader:(GLuint)shader fromSource:(NSString *)path;
@end


@implementation ShaderProgram

@synthesize isLinked, program;

-(id)initWithVertexShader:(NSString *)pathVertex
		   fragmentShader:(NSString *)pathFragment
		   geometryShader:(NSString *)pathGeometry {
	
	if ( self = [super init] ) {
		
		isLinked = NO;
		
		NSString *stringVertex = [NSString stringWithContentsOfFile: pathVertex encoding: NSUTF8StringEncoding error: NULL];
		NSString *stringFragment = [NSString stringWithContentsOfFile: pathFragment encoding: NSUTF8StringEncoding error: NULL];
		
		if (stringVertex == nil) {
			NSLog(@"Could not open vertex program at '%@'", pathVertex);
			return nil;
		}
		
		if (stringFragment == nil) {
			NSLog(@"Could not open fragment program at '%@'", pathFragment);
			return nil;
		}
		
		program = glCreateProgram();
		
		vertexShader = glCreateShader(GL_VERTEX_SHADER);
		if (![self compileShader: vertexShader fromSource: stringVertex]) {
			NSLog(@"Vertex Shader failed to compile: %@", [self vertexShaderInformationLog]);
			return nil;
		}
		glAttachShader(program, vertexShader);
		
		fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);		
		if (![self compileShader: fragmentShader fromSource: stringFragment]) {
			NSLog(@"Fragment Shader failed to compile: %@", [self fragmentShaderInformationLog]);
			return nil;
		}		
		glAttachShader(program, fragmentShader);
		
		if ( pathGeometry != nil ) {
			geometryShader = glCreateShader(GL_GEOMETRY_SHADER_EXT);
			NSString *stringGeometry = \
				[NSString stringWithContentsOfFile: pathGeometry encoding: NSUTF8StringEncoding error: NULL];
			if (![self compileShader: geometryShader fromSource: stringGeometry]) {
				NSLog(@"Geometry Shader failed to compile: %@", [self geometryShaderInformationLog]);
				return nil;
			}
			glAttachShader(program, geometryShader);
		}
		else {
			geometryShader = 0;
		}
		
		
		// flag for deletion (deleted when program is deleted)
		if (vertexShader) glDeleteShader(vertexShader);
		if (fragmentShader) glDeleteShader(fragmentShader);
		if (geometryShader) glDeleteShader(geometryShader);
		
	}
	
	return self;
	
	
}

-(void)setInputType:(GLint)inputType {
	glProgramParameteriEXT(program, GL_GEOMETRY_INPUT_TYPE_EXT, inputType);
}

-(void)setOutputType:(GLint)outputType {
	glProgramParameteriEXT(program, GL_GEOMETRY_OUTPUT_TYPE_EXT, outputType);
}

-(void)setNumVerticesOut:(int)num {
	glProgramParameteriEXT(program, GL_GEOMETRY_VERTICES_OUT_EXT, num);
}

-(id)initWithVertexShader:(NSString *)pathVertex fragmentShader:(NSString *)pathFragment {

	return [self initWithVertexShader: pathVertex fragmentShader: pathFragment geometryShader: nil];
	
}

-(BOOL)compileShader:(GLuint)shader fromSource:(NSString *)source {

	const char * cString = [source UTF8String];
	
	int length = [source length];
	
	glShaderSource(shader, 1, &cString, &length);
	glCompileShader(shader);
	
	GLint compiled;
	glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
	
	return compiled == GL_TRUE;
	
}

-(BOOL)linkProgram {
	
	glLinkProgram(program);
	
	int linkStatus;
	glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
	if ( !linkStatus ) {
		return NO;
	}
	
	isLinked = YES;
	return YES;
	
}

-(void)useProgram {
	glUseProgram(program);
}

#pragma mark -

-(int)locationForUniform:(NSString *)uniformName {
	return glGetUniformLocation(program, [uniformName UTF8String]);
}

-(void)bindAttribute:(NSString *)attribute toLocation:(GLuint)location {
	glBindAttribLocation(program, location, [attribute UTF8String]);
}

-(int)getLocationForAttribute:(NSString *)attribute {
	return glGetAttribLocation(program, [attribute UTF8String]);
}

#pragma mark -

// get the error log

-(NSString *)informationLogForShader:(GLuint)shader {
	
	int length;
	glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length);
	char infoLog[length];
	glGetShaderInfoLog(shader, length, NULL, infoLog);
	return [NSString stringWithUTF8String: infoLog];
	
}

-(NSString *)fragmentShaderInformationLog {
	return [self informationLogForShader: fragmentShader];
}

-(NSString *)vertexShaderInformationLog {
	return [self informationLogForShader: vertexShader];
}

-(NSString *)geometryShaderInformationLog {
	return [self informationLogForShader: geometryShader];
}


-(NSString *)programInformationLog {
	
	int length;
	glGetProgramiv(program, GL_INFO_LOG_LENGTH, &length);
	char infoLog[length];
	glGetProgramInfoLog(program, length, NULL, infoLog);
	return [NSString stringWithUTF8String: infoLog];
	
}

// validates program against current GL state (will the program run?)
// overwrites information log if FALSE
-(BOOL)validateProgram {
	
	glValidateProgram(program);
	int status;
	glGetProgramiv(program, GL_VALIDATE_STATUS, &status);
	return (status == GL_TRUE);
	
}

#pragma mark -

-(void)dealloc {
	// automatically detaches shaders, deleting them
	glDeleteProgram(program);
	[super dealloc];
}


@end
